package graph.dependencies;

import exceptions.DependencyException;
import graph.visitors.MethodInvocationVisitor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import tasks.SplitResponsibilitiesTask;
import tasks.SplitResponsibilitiesTask2;
import tasks.TaskPool;
import clustering.MethodDistanceFinder;
import clustering.MethodRelationshipDeterminer;

import com.aliasi.cluster.SingleLinkClusterer;
import com.aliasi.util.Distance;

public class ResponsibilityDependency implements IDependency {

	private TypeDeclaration _typeDeclaration = null;
	private Set<Set<Entry<MethodDeclaration, Set<ITypeBinding>>>> _clusters = null;

	public ResponsibilityDependency(TypeDeclaration node) {
		_typeDeclaration  = node;
	}
	
	private Set<ITypeBinding> getInvocationTypes(List<MethodInvocation> invocations) throws DependencyException {
		try {
			Set<ITypeBinding> types = new HashSet<ITypeBinding>();
			for (MethodInvocation mI : invocations) {
				boolean typeAlreadyAdded = false;
				for (ITypeBinding iTB : types)
					if (mI.resolveMethodBinding().getDeclaringClass().isEqualTo(iTB))
						typeAlreadyAdded=true;
				if (!typeAlreadyAdded)
					types.add(mI.resolveMethodBinding().getDeclaringClass());
			}
			return types;
		} catch (Exception e) {
			String error = "ResponsibilityDependency>Error while getting types using by "+_typeDeclaration.getName();
			System.out.println(error);
			throw new DependencyException(error);
		}
	}

	@Override
	public boolean isLegal() throws DependencyException {
		try {
			MethodDeclaration[] methods = _typeDeclaration.getMethods();
			Map<MethodDeclaration, Set<ITypeBinding>> method2Invocations = new HashMap<MethodDeclaration, Set<ITypeBinding>>();
			MethodInvocationVisitor methodInvocationSearcher = new MethodInvocationVisitor ();
			for (MethodDeclaration methodDeclaration : methods) {
				methodInvocationSearcher.getMethodInvocations().clear();
				methodDeclaration.accept(methodInvocationSearcher);
				method2Invocations.put(methodDeclaration, getInvocationTypes(methodInvocationSearcher.getMethodInvocations()));
			}
			if (method2Invocations.isEmpty())
				return true;
			Distance<Entry<MethodDeclaration, Set<ITypeBinding>>> methodDistanceFinder = new MethodDistanceFinder();
			SingleLinkClusterer<Map.Entry<MethodDeclaration, Set<ITypeBinding>>> clusterer =
				new SingleLinkClusterer<Map.Entry<MethodDeclaration, Set<ITypeBinding>>>(methodDistanceFinder);
			_clusters  = clusterer.hierarchicalCluster(method2Invocations.entrySet()).partitionDistance(100);
			
			if (_clusters.size()>1)
				return false;
			return true;
		} catch (DependencyException e) {
			throw e;
		}catch (Exception e) {
			String error = "ResponsibilityDependency>Error while checking if dependency is legal";
			System.out.println(error);
			throw new DependencyException(error);
		}
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void scheduleFixTask() throws DependencyException {
		if (!isLegal()) {
			Set<Map<IMethodBinding, Set<ITypeBinding>>> clusters = new HashSet<Map<IMethodBinding, Set<ITypeBinding>>>();
			for (Set<Entry<MethodDeclaration, Set<ITypeBinding>>> cluster : _clusters) {
				Map<IMethodBinding, Set<ITypeBinding>> methodToClassesUsed = new HashMap<IMethodBinding, Set<ITypeBinding>>();
				for (Entry<MethodDeclaration, Set<ITypeBinding>> keyValPair : cluster)
					methodToClassesUsed.put(keyValPair.getKey().resolveBinding(), keyValPair.getValue());
				clusters.add(methodToClassesUsed);
			}
			TaskPool.getInstance().add(new SplitResponsibilitiesTask2(clusters,_typeDeclaration));
		}
	}

}
